package cn.xiongxf.service.impl;

import cn.xiongxf.constant.RedisKeyConstant;
import cn.xiongxf.domain.CourseType;
import cn.xiongxf.dto.CourseTypeCrumbsDto;
import cn.xiongxf.mapper.CourseTypeMapper;
import cn.xiongxf.service.ICourseTypeService;
import cn.xiongxf.util.AssertUtil;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author xiongxiaofu
 * @since 2022-08-16
 */
@Service
@Slf4j
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Override
    // @CacheEvict删除缓存
    @Caching(evict = {@CacheEvict(cacheNames = RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY, key = "'key'"),
            @CacheEvict(cacheNames = RedisKeyConstant.COURSE_TYPE_CRUMBS_KEY, key = "#id")})
    public boolean deleteById(Serializable id) {
        // 先执行MySQL的写操作
        boolean b = super.deleteById(id);
//        // 再清空Redis中缓存的【类型树】
//        redisTemplate.delete(RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY);
        return b;
    }

    @Override
    @Caching(evict = {@CacheEvict(cacheNames = RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY, key = "'key'"),
            @CacheEvict(cacheNames = RedisKeyConstant.COURSE_TYPE_CRUMBS_KEY, key = "#entity.id")})
    public boolean insert(CourseType entity) {
        // 先执行MySQL的写操作
        boolean b = super.insert(entity);
//        // 再清空Redis中缓存的【类型树】
//        redisTemplate.delete(RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY);
        return b;
    }

    @Override
    @Caching(evict = {@CacheEvict(cacheNames = RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY, key = "'key'"),
                      @CacheEvict(cacheNames = RedisKeyConstant.COURSE_TYPE_CRUMBS_KEY, key = "#entity.id")})
    public boolean updateById(CourseType entity) {
        // 先执行MySQL的写操作
        boolean b = super.updateById(entity);
//        // 再清空Redis中缓存的【类型树】
//        redisTemplate.delete(RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY);
        return b;
    }


    /**
     * 获取【类型树】
     * @return treeData
     */
    @Override
    // 添加缓存  aa::bb   特别注意：缓存注解不能加在内部方法上，比如：方法A调用方法B，在A方法上打注解即可，给方法B加上缓存注解会失效，因为内部方法调用代理会失效
    @Cacheable(cacheNames = RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY, key = "'key'")
    public List<CourseType> treeData() {
//        // 从Redis中获取缓存的【类型树】
//        List<CourseType> treeData = (List<CourseType>) redisTemplate.opsForValue().get(RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY);
//        if (treeData == null){
//            // MySQL执行写操作后清空了Redis中缓存的【类型树】，重新从MySQL中获取【类型树】并缓存到Redis
//            log.debug("从MySQL中获取【类型树】并缓存到Redis=======================================");
//            treeData = getTreeDataFromDB();
//            redisTemplate.opsForValue().set(RedisKeyConstant.COURSE_TYPE_TREEDATA_KEY,treeData);
//        }
        return getTreeDataFromDB();
    }

    // 抽取方法：从MySQL中获取【类型树】
    private List<CourseType> getTreeDataFromDB() {
        // 获取所有课程类型
        List<CourseType> courseTypes = selectList(null);
        // 将类型id和类型对象存入map中,方便查询
//        HashMap<Long, CourseType> map = new HashMap<>();
//        for (CourseType courseType : courseTypes) {
//            map.put(courseType.getId(),courseType);
//        }

        // lamda表达式获取map(高级)
        Map<Long,CourseType> map = courseTypes
                .stream()
                .collect(Collectors.toMap(CourseType::getId,CourseType -> CourseType));

        // 创建树集合
        List<CourseType> treeData = new ArrayList<>();
        for (CourseType courseType : courseTypes) {
            if (courseType.getPid().equals(0L)){
                // 顶级类型,直接添加到树中
                treeData.add(courseType);
            }else {
                // 从map中获取父类型
                CourseType parentCourseType = map.get(courseType.getPid());
                // 把自己放入父类型的children集合中
                parentCourseType.getChildren().add(courseType);
            }
        }
        // 返回树集合
        return treeData;
    }


    /**
     * 获取【面包屑】
     * @param id 课程类型ID
     * @return crumbs
     */
    @Override
    // 方法无参时，key可写死；方法有参时，key需绑定动态参数
    @Cacheable(cacheNames = RedisKeyConstant.COURSE_TYPE_CRUMBS_KEY, key = "#id")
    public List<CourseTypeCrumbsDto> crumbs(Long id) {
        List<CourseTypeCrumbsDto> crumbs = new ArrayList<>();
        // 选中的类型
        CourseType courseType = selectById(id);
        // 若选中类型不存在，抛异常
        AssertUtil.isNotNull(courseType, "该课程类型不存在！");
        // 类型路径    1037.1039.1040
        String path = courseType.getPath();
        // 类型路径转数组
        String[] ids = path.split("\\.");  // "."是正则表达式符号，不加转译符号\\的话，得到的是空数组

        List<CourseType> courseTypes = null;
        for (int i = 0 ; i <= ids.length-1; i++){
            // 当前类型
            courseType = selectById(ids[i]);
            // 父类型id
//            String pid = "0";  // 顶级类型
//            if (i != 0 )  pid = ids[i - 1];  // 非顶级类型
            String pid = i == 0 ? "0" : ids[i - 1];   // 善用三目运算
            Wrapper<CourseType> query = new EntityWrapper<>();
            query.eq("pid",  pid);
            query.notIn("id", ids[i]);  // 排除当前类型
            // 其它同级类型
            courseTypes = selectList(query);
//            courseTypes.remove(courseType);  // 用remove方法必需重写equals ！！！
            // 封装到dto对象
            CourseTypeCrumbsDto courseTypeCrumbsDto = new CourseTypeCrumbsDto(courseType, courseTypes);
            // dto对象添加到【面包屑】集合
            crumbs.add(courseTypeCrumbsDto);
        }
        return crumbs;
    }


}
